﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;

namespace WWLauncher.Util
{

    public class Win32PowSchemasWrapper
    {

        public Guid GetActiveGuid()
        {
            Guid activeSchema;
            IntPtr guidPtr = IntPtr.Zero;

            try
            {
                var errCode = PowerGetActiveScheme(IntPtr.Zero, out guidPtr);

                if (errCode != 0) { throw new Exception($"GetActiveGuid() failed with code {errCode}"); }
                if (guidPtr == IntPtr.Zero) { throw new Exception("GetActiveGuid() returned null pointer for GUID"); }

                activeSchema = (Guid)Marshal.PtrToStructure(guidPtr, typeof(Guid))!;
            }
            finally
            {
                if (guidPtr != IntPtr.Zero) { LocalFree(guidPtr); }
            }

            return activeSchema;
        }

        public void SetActiveGuid(Guid guid)
        {
            var errCode = PowerSetActiveScheme(IntPtr.Zero, ref guid);
            if (errCode != 0) { throw new Exception($"SetActiveGuid() failed with code {errCode}"); }
        }

        public string GetPowerPlanName(Guid guid)
        {
            string name;

            IntPtr bufferPointer = IntPtr.Zero;
            uint bufferSize = 0;

            try
            {
                var errCode = PowerReadFriendlyName(IntPtr.Zero, ref guid, IntPtr.Zero, IntPtr.Zero, bufferPointer, ref bufferSize);
                if (errCode != 0) { throw new Exception($"GetPowerPlanName() failed when getting buffer size with code {errCode}"); }

                if (bufferSize <= 0) { return String.Empty; }
                bufferPointer = Marshal.AllocHGlobal((int)bufferSize);

                errCode = PowerReadFriendlyName(IntPtr.Zero, ref guid, IntPtr.Zero, IntPtr.Zero, bufferPointer, ref bufferSize);
                if (errCode != 0) { throw new Exception($"GetPowerPlanName() failed when getting buffer pointer with code {errCode}"); }

                name = Marshal.PtrToStringUni(bufferPointer);
            }
            finally
            {
                if (bufferPointer != IntPtr.Zero) { Marshal.FreeHGlobal(bufferPointer); }
            }

            return name;
        }

        private const int ERROR_NO_MORE_ITEMS = 259;

        public List<PowerSchema> Schemas { get; set; }

        public List<PowerSchema> GetCurrentSchemas()
        {
            var powerSchemas = getAllPowerSchemaGuids().Select(guid => new PowerSchema(GetPowerPlanName(guid), guid)).ToList();
            return powerSchemas;
        }

        private IEnumerable<Guid> getAllPowerSchemaGuids()
        {
            var schemeGuid = Guid.Empty;

            uint sizeSchemeGuid = (uint)Marshal.SizeOf(typeof(Guid));
            uint schemeIndex = 0;

            while (true)
            {
                uint errCode = PowerEnumerate(IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, (uint)AccessFlags.ACCESS_SCHEME, schemeIndex, ref schemeGuid, ref sizeSchemeGuid);
                if (errCode == ERROR_NO_MORE_ITEMS) { yield break; }
                if (errCode != 0) { throw new Exception($"GetPowerSchemeGUIDs() failed when getting buffer pointer with code {errCode}"); }

                yield return schemeGuid;
                schemeIndex++;
            }
        }

        #region EnumerationEnums
        public enum AccessFlags : uint
        {
            ACCESS_SCHEME = 16,
            ACCESS_SUBGROUP = 17,
            ACCESS_INDIVIDUAL_SETTING = 18
        }
        #endregion
        public void UpdateSchemas()
        {
            var currSchemaGuid = GetActiveGuid();
            var newSchemas = GetCurrentSchemas();
            Schemas= newSchemas;
            //Add and update new / changed schemas
            foreach (var newSchema in newSchemas)
            {
                var originalSchema = Schemas.FirstOrDefault(sch => sch.Guid == newSchema.Guid);
                if (originalSchema == null) { insertNewSchema(newSchemas, newSchema); originalSchema = newSchema; }

                if (newSchema.Guid == currSchemaGuid && originalSchema?.IsActive != true)
                { setNewCurrSchema(originalSchema); }

                if (originalSchema?.Name != newSchema.Name)
                { ((PowerSchema)originalSchema).Name = newSchema.Name; }
            }

            if (Schemas.All(sch => currSchemaGuid != sch.Guid))
            {
                noSchemaIsActive();
            }

            //remove old schemas
            var schemasToBeRemoved = new List<PowerSchema>();
            foreach (var oldSchema in Schemas)
            {
                if (newSchemas.FirstOrDefault(sch => sch.Guid == oldSchema.Guid) == null)
                { schemasToBeRemoved.Add(oldSchema); }
            }
            schemasToBeRemoved.ForEach(sch => Schemas.Remove(sch));
        }
        private void noSchemaIsActive()
        {
            var oldActive = Schemas.FirstOrDefault(sch => sch.IsActive);
            if (oldActive != null)
            {
                ((PowerSchema)oldActive).IsActive = false;

                //CurrentSchema = null;
                //RaisePropertyChangedEvent(nameof(CurrentSchema));
            }
        }
        private void insertNewSchema(List<PowerSchema> newSchemas, PowerSchema newSchema)
        {
            var insertToIndex = Math.Min(newSchemas.IndexOf(newSchema), Schemas.Count);
            Schemas.Insert(insertToIndex, newSchema);
        }

        private void setNewCurrSchema(IPowerSchema newActiveSchema)
        {
            var oldActiveSchema = Schemas.FirstOrDefault(sch => sch.IsActive);

            ((PowerSchema)newActiveSchema).IsActive = true;
            //CurrentSchema = newActiveSchema;
            //RaisePropertyChangedEvent(nameof(CurrentSchema));

            //can cause change change of curr power schema: http://stackoverflow.com/questions/42703092/remove-selection-when-selected-item-gets-deleted-from-listbox
            if (oldActiveSchema != null) { oldActiveSchema.IsActive = false; }
        }
        #region DLL imports

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr LocalFree(IntPtr hMem);

        [DllImport("powrprof.dll", EntryPoint = "PowerSetActiveScheme")]
        private static extern uint PowerSetActiveScheme(IntPtr UserPowerKey, ref Guid ActivePolicyGuid);

        [DllImport("powrprof.dll", EntryPoint = "PowerGetActiveScheme")]
        private static extern uint PowerGetActiveScheme(IntPtr UserPowerKey, out IntPtr ActivePolicyGuid);

        [DllImport("powrprof.dll", EntryPoint = "PowerReadFriendlyName")]
        private static extern uint PowerReadFriendlyName(IntPtr RootPowerKey, ref Guid SchemeGuid, IntPtr SubGroupOfPowerSettingsGuid, IntPtr PowerSettingGuid, IntPtr BufferPtr, ref uint BufferSize);

        [DllImport("PowrProf.dll")]
        public static extern uint PowerEnumerate(IntPtr RootPowerKey, IntPtr SchemeGuid, IntPtr SubGroupOfPowerSettingGuid, UInt32 AcessFlags, UInt32 Index, ref Guid Buffer, ref UInt32 BufferSize);

        #endregion
    }

}
